package core;

import java.util.Iterator;

import core.reports.IReportVisitor;
import core.reports.TraversingType;


/**
 * This class represents a Product in our data model.
 * 
 * @author Sabrina
 *
 */
public class Product extends ModelObject implements Comparable<Product>
{
	private static final long serialVersionUID = 2717222805698104577L;
	private String _description;
	private Barcode _barcode;
	private Amount _size;
	private int _shelfLife;
	private int _threeMonthSupply;
	
	/**
	 * checks whether a product can be created with the arguments
	 * NOTE: does not check whether bar code is unique
	 * 
	 * @param barcode the product's bar code (must be non-empty, must be product type)
	 * @param description description of the product (must be non-empty)
	 * @param size the size of one unit of product (ie 6 oz)			
	 * @param shelfLife the shelf-life in months of the product (must be non-negative)
	 * @param threeMonthSupply the amount needed for 3 months	(must be non-negative)
	 * 
	 * @return returns true if product can be created with these arguments
	 */
	public static boolean canCreate(Barcode barcode, String description, Amount size, 
									int shelfLife, int threeMonthSupply)
	{
		//no preconditions
		if (barcode == null)
			return false;
		if (barcode.getType() != BarcodeType.PRODUCT)
			return false;
		if (canSetDescription(description) &&
				canSetSize(size)	&&
				canSetShelfLife(shelfLife) &&
				canSetThreeMonthSupply(threeMonthSupply))
		{
			return true;
		}
		else
			return false;
		
	}
	
	public static boolean canCreate(String barcode, String description, String size, 
			String unit, int shelfLife, int threeMonthSupply) {
		
		boolean possible = false;
		//check the size first
		try {
			Float.parseFloat(size);
		}
		catch (NumberFormatException e) {
			return false;
		}
		
		try {
			possible = canCreate(new Barcode(barcode, BarcodeType.PRODUCT), description,
				new Amount(size, unit), shelfLife, threeMonthSupply);
		}
		catch (IllegalArgumentException e) {
			possible = false;
		}
		
		return possible;
	}

	/**
	 * Constructor 
	 * 
	 * @param barcode the product's bar code
	 * @param description description of the product
	 * @param size the size of one unit of product		
	 * @param shelfLife the shelf-life in months of the product
	 * @param threeMonthSupply the amount needed for 3 months
	 * 
	 * @pre canCreate(barcode, description, size, shelfLife, threeMonthSupply);
	 * 
	 */
	public Product(Barcode barcode, String description, Amount size, int shelfLife, 
						int threeMonthSupply) throws IllegalArgumentException
	{
		if(!canCreate(barcode, description, size, shelfLife, threeMonthSupply)) {
			throw new IllegalArgumentException("Illegal product data.");
		}
		
		_barcode = barcode;
		setDescription(description);
		setSize(size);
		setShelfLife(shelfLife);
		setThreeMonthSupply(threeMonthSupply);
	}
	
	public Product(String barcode, String description, String size, String unit,
			int shelfLife, int threeMonthSupply) throws IllegalArgumentException {
		this(new Barcode(barcode, BarcodeType.PRODUCT), description,
				new Amount(size, unit), shelfLife, threeMonthSupply);
	}
	
	/**
	 * Checks whether can edit the product to match the passed product
	 * 
	 * @param newInfo product that want current product to match
	 * 
	 * @return false if cannot edit the current product to match passed product, 
	 * 			else returns true
	 * 			cannot edit if any of the following conditions exists:
	 * 				newInfo is null
	 * 				barcodes do not match
	 */
	public boolean canEdit(Product newInfo)
	{
		//no preconditions
		if (newInfo == null)
			return false;
		if (!newInfo.getBarcode().equals(_barcode))
			return false;
		return true;
	}

	/**
	 * allows editing of the product description, size, shelf life, or 3 month supply
	 * 
	 * @param newInfo Product with the desired new information
	 * 
	 *@pre canEdit(newInfo);
	 */
	protected void editProduct(Product newInfo)
	{
		assert canEdit(newInfo);
		
		setDescription(newInfo.getDescription());
		setSize(newInfo.getSize());
		setShelfLife(newInfo.getShelfLife());
		setThreeMonthSupply(newInfo.getThreeMonthSupply());
	}

	/**
	 * get the bar code of the product
	 * 
	 * @return the product's bar code
	 */
	public Barcode getBarcode() 
	{
		//no preconditions
		return _barcode;
	}

	/**
	 * get the product's description
	 * 
	 * @return the product's description
	 */
	public String getDescription() 
	{
		//no preconditions
		return _description;
	}

	/**
	 * Checks whether the string is a valid description
	 * 
	 * @param desc the new description
	 * 
	 * @return true if valid description, else returns false
	 */
	public static boolean canSetDescription(String desc)
	{
		//no preconditions
		if (desc.equals(""))
			return false;
		else
			return true;
	}

	/**
	 * Sets the description of the product
	 * 
	 * @param desc the new description
	 * 
	 * @pre canSetDescription(desc)
	 */
	protected void setDescription(String desc)
	{
		assert canSetDescription(desc);
		_description = desc;
	}
	
	/**
	 * get the size of one unit of product
	 * 
	 * @return size of product's unit
	 */
	public Amount getSize() 
	{
		//no preconditions
		return _size;
	}

	/**
	 * Return the quantity for one unit
	 * 
	 * @return the quantity
	 */
	public float getQuantityForOneUnit()
	{
		return _size.getQuantity();
	}

	/**
	 * Returns the measurement for the product
	 * 
	 * @return the measurement
	 */
	public String getUnitOfMeasurement()
	{
		return _size.getUnit().toString();
	}

	/**
	 * Checks to see if the unit size is valid
	 * 
	 * @param amt the new unit size for the product
	 * 
	 * @return returns true if amt is not null, else returns false
	 */
	public static boolean canSetSize(Amount amt)
	{
		//no preconditions
		if (amt == null)
			return false;
		if (amt.getQuantity() <= 0)
			return false;
		else
			return true;
	}

	/**
	 * Sets the unit size of the product
	 * 
	 * @param amt the new unit size
	 * 
	 * @pre canSetSize(amt)
	 */
	protected void setSize(Amount amt)
	{
		assert canSetSize(amt);
		_size = amt;
	}

	/**
	 * get the shelf life of the product (in months)
	 * 
	 * @return product's shelf-life in months
	 */
	public int getShelfLife() 
	{
		//no preconditions
		return _shelfLife;
	}

	/**
	 * checks if the shelfLife is valid
	 * 
	 * @param shelfLife the new shelfLife
	 * 
	 * @return Returns true if shelfLife is nonnegative, else returns false
	 */
	public static boolean canSetShelfLife(int shelfLife)
	{
		//no preconditions
		if (shelfLife < 0)
			return false;
		else
			return true;
	}

	/**
	 * sets the shelf life of the product in months
	 * 
	 * @param shelfLife the new shelf life
	 * 
	 * @pre canSetShelfLife(shelfLife)
	 */
	protected void setShelfLife(int shelfLife)
	{
		assert canSetShelfLife(shelfLife);
		_shelfLife = shelfLife;
	}
	
	/**
	 * get the number of this product for a 3-month supply
	 * 
	 * @return the number of product units needed for 3 months
	 */
	public int getThreeMonthSupply() 
	{
		//no preconditions
		return _threeMonthSupply;
	}
	
	/**
	 * checks if the mo3Supp is valid
	 * 
	 * @param mo3Supp new 3 month supply to be checked
	 * 
	 * @return returns true if mo3Supp nonnegative, else returns false
	 */
	public static boolean canSetThreeMonthSupply(int mo3Supp)
	{
		//no preconditions
		if (mo3Supp < 0)
			return false;
		else
			return true;
	}

	/**
	 * Sets the three month supply for the product
	 * 
	 * @param mo3Supp number of units needed for 3 months
	 * 
	 * @pre canSetThreeMonthSupply(mo3Supp)
	 */
	protected void setThreeMonthSupply(int mo3Supp)
	{
		assert canSetThreeMonthSupply(mo3Supp);
		_threeMonthSupply = mo3Supp;
	}

	/**
	 * compares this Product with product o
	 * 
	 * @param o the Product to compare to this Product
	 * 
	 * @return negative if less than, 0 if equal, and positive if greater than
	 */
	@Override
	public int compareTo(Product o) 
	{
		//no preconditions
		
		if (o == this)
			return 0;
		if (_description.equals(o.getDescription()))
		{
			Barcode otherBarcode = o.getBarcode();
			return _barcode.compareTo(otherBarcode);
		}
		else
			return _description.compareTo(o.getDescription());
	}
	
	/**
	 * Accepts a Visitor
	 * @param v the visiting IReportVisitor
	 */
	public void accept(IReportVisitor v) {
		v.visitProduct(this);
	}
	
	/**
	 * Accepts a visitor, then calls accept on all of this Product's Items.
	 * @param v visitProductGroup
	 */
	public void acceptRecursively(IReportVisitor v) {
		if (v.getType() == TraversingType.PREORDER)
		{
			v.visitProduct(this);
			Iterator<Item> iIter = ItemManager.instance().getAllItemsInProduct(this);
			while(iIter.hasNext()) {
				v.visitItem(iIter.next());
			}
		}
		else
		{
			Iterator<Item> iIter = ItemManager.instance().getAllItemsInProduct(this);
			while(iIter != null && iIter.hasNext()) {
				v.visitItem(iIter.next());
			}
			v.visitProduct(this);
		}
	}
}
